package cn.iocoder.yudao.module.product.spu.service;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.business.basic.dao.query.CoreLambdaQueryWrapper;
import cn.iocoder.yudao.framework.business.basic.enums.SortTypeEnum;
import cn.iocoder.yudao.framework.business.basic.pojo.dto.LocationQueryDTO;
import cn.iocoder.yudao.framework.business.basic.pojo.dto.SortQueryDTO;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.framework.common.util.number.MoneyUtils;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.datapermission.core.annotation.DataPermission;
import cn.iocoder.yudao.module.api.infrastructure.config.ConfigApiV2;
import cn.iocoder.yudao.module.api.member.level.MemberLevelApi;
import cn.iocoder.yudao.module.api.product.spu.enums.ProductSpuStatusEnum;
import cn.iocoder.yudao.module.api.product.spu.enums.ProductSpuTabTypeEnum;
import cn.iocoder.yudao.module.api.promotion.groupon.GrouponPackageApi;
import cn.iocoder.yudao.module.api.promotion.groupon.dto.GrouponPackageRespDTO;
import cn.iocoder.yudao.module.api.shop.category.ShopCategoryApi;
import cn.iocoder.yudao.module.api.shop.category.eums.ShopCategoryCodeEnum;
import cn.iocoder.yudao.module.api.shop.info.ShopInfoApi;
import cn.iocoder.yudao.module.api.shop.info.dto.ShopInfoDTO;
import cn.iocoder.yudao.module.product.brand.dal.dataobject.ProductBrandDO;
import cn.iocoder.yudao.module.product.brand.service.ProductBrandService;
import cn.iocoder.yudao.module.product.category.service.ProductCategoryService;
import cn.iocoder.yudao.module.product.history.service.ProductBrowseHistoryService;
import cn.iocoder.yudao.module.product.sku.dal.dataobject.ProductSkuDO;
import cn.iocoder.yudao.module.product.sku.service.ProductSkuService;
import cn.iocoder.yudao.module.product.sku.vo.AppProductSkuRespVO;
import cn.iocoder.yudao.module.product.sku.vo.ProductSkuSaveReqVO;
import cn.iocoder.yudao.module.product.spu.bo.ShopIdDistanceBO;
import cn.iocoder.yudao.module.product.spu.bo.ShopIdSpuCountBO;
import cn.iocoder.yudao.module.product.spu.convert.ProductSpuConvert;
import cn.iocoder.yudao.module.product.spu.dal.dataobject.ProductSpuDO;
import cn.iocoder.yudao.module.product.spu.dal.mysql.ProductSpuMapper;
import cn.iocoder.yudao.module.product.spu.vo.*;
import com.google.common.collect.Maps;
import io.micrometer.common.lang.Nullable;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;

import java.util.*;
import java.util.stream.Collectors;

import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserLastShopId;
import static cn.iocoder.yudao.framework.util.HttpUtils.queryStringToLongs;
import static cn.iocoder.yudao.module.api.product.ErrorCodeConstants.*;
import static cn.iocoder.yudao.module.product.category.dal.dataobject.ProductCategoryDO.CATEGORY_TOP_LEVEL_1;

/**
 * 产品 SPU Service 实现类
 *
 * @author 芋道源码
 */
@Service
@Validated
@Slf4j
public class ProductSpuServiceImpl implements ProductSpuService {

    @Resource
    private ProductSpuMapper productSpuMapper;

    @Resource
    @Lazy // 延迟加载，避免循环依赖
    private ProductSkuService productSkuService;
    @Resource
    private ProductBrandService productBrandService;
    @Resource
    private ProductCategoryService productCategoryService;
    @Resource
    private ProductBrowseHistoryService productBrowseHistoryService;

    @Resource
    @Lazy // 延迟加载，避免循环依赖
    private ShopInfoApi shopInfoApi;
    @Resource
    @Lazy // 延迟加载，避免循环依赖
    private ShopCategoryApi shopCategoryApi;
    @Resource
    @Lazy // 延迟加载，避免循环依赖
    private MemberLevelApi memberLevelApi;
    @Resource
    private ConfigApiV2 configApiV2;
    @Resource
    @Lazy // 延迟加载，避免循环依赖
    private GrouponPackageApi grouponPackageApi;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Long createSpu(ProductSpuSaveReqVO createReqVO) {
        Long shopId = getLoginUserLastShopId();
        createReqVO.setId(null);
        // 校验分类、品牌
        productBrandService.validateProductBrand(createReqVO.getBrandId());

        ProductSpuDO spu = ProductSpuConvert.INSTANCE.convert(createReqVO);
        if (StrUtil.isBlank(createReqVO.getDescription())) {
            spu.setDescription("<p>暂无</p>");
        }
        spu.setGiveIntegral(0);
        spu.setIsBrokerage(false);
        spu.setShopId(shopId);
        spu.setType(shopCategoryApi.getProductTypeByShopId(shopId));
        List<ProductSkuSaveReqVO> skus = createReqVO.getSkus();
        // 初始化 SPU 中 SKU 相关属性
        initSpuFromSkus(spu, skus);
        // 插入 SPU
        productSpuMapper.insert(spu);
        // 校验 SKU
        productSkuService.validateSkuList(shopId, spu.getId(), skus, createReqVO.getSpecType());
        // 插入 SKU
        productSkuService.createSkuList(shopId, spu.getId(), skus);
        // 返回
        return spu.getId();
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateSpu(ProductSpuSaveReqVO updateReqVO) {
        // 校验 SPU 是否存在
        ProductSpuDO spuDO = validateSpuExists(updateReqVO.getId());
        // 校验分类、品牌
        validateCategory(updateReqVO.getCategoryId());
        productBrandService.validateProductBrand(updateReqVO.getBrandId());
        // TODO 校验是否存在营销活动，有营销活动时不能修改规格信息

        // 更新 SPU
        ProductSpuDO updateObj = ProductSpuConvert.INSTANCE.convert(updateReqVO);
        if (StrUtil.isBlank(updateReqVO.getDescription())) {
            updateObj.setDescription("<p>暂无</p>");
        }
        updateObj.setStatus(spuDO.getStatus());
        updateObj.setType(shopCategoryApi.getProductTypeByShopId(spuDO.getShopId()));
        List<ProductSkuSaveReqVO> skus = updateReqVO.getSkus();
        initSpuFromSkus(updateObj, skus);
        if (CollUtil.isEmpty(updateObj.getSliderPicUrls())) {
            // 不允许删掉轮播图
            updateObj.setSliderPicUrls(null);
        }
        if (StrUtil.isEmpty(updateObj.getDescription())) {
            // 不允许删掉详情
            updateObj.setDescription(null);
        }
        productSpuMapper.updateById(updateObj);
        // 校验SKU
        productSkuService.validateSkuList(spuDO.getShopId(), spuDO.getId(), skus, updateReqVO.getSpecType());
        // 批量更新 SKU
        productSkuService.updateSkuList(updateObj.getId(), skus);
    }

    /**
     * 基于 SKU 的信息，初始化 SPU 的信息
     * 主要是计数相关的字段，例如说市场价、最大最小价、库存等等
     *
     * @param spu  产品 SPU
     * @param skus 产品 SKU 数组
     */
    private void initSpuFromSkus(ProductSpuDO spu, List<ProductSkuSaveReqVO> skus) {
        // sku 单价最低的产品的销售价格
        spu.setPrice(getMinValue(skus, ProductSkuSaveReqVO::getPrice));
        // sku 单价最低的产品的成本价格
        spu.setCostPrice(getMinValue(skus, ProductSkuSaveReqVO::getCostPrice));
        // sku 单价最低的产品的市场价格
        spu.setMarketPrice(Math.max(spu.getPrice(), NumberUtil.nullToZero(getMinValue(skus, ProductSkuSaveReqVO::getMarketPrice))));
        // sku 单价最低的产品的会员价格
        spu.setVipPrice(getMinValue(skus, ProductSkuSaveReqVO::getVipPrice));
        // sku 单价最低的产品的厂家零售价格
        spu.setWholesaleForRetailPrice(getMinValue(skus, ProductSkuSaveReqVO::getWholesaleForRetailPrice));
        // skus 库存总数
        spu.setStock(getSumValue(skus, ProductSkuSaveReqVO::getStock, Integer::sum));
        // 若是 spu 已有状态则不处理
        if (spu.getStatus() == null) {
            spu.setStatus(ProductSpuStatusEnum.ENABLE.getStatus()); // 默认状态为上架
            spu.setSalesCount(0); // 默认产品销量
            spu.setBrowseCount(0); // 默认产品浏览量
        }
    }

    /**
     * 校验产品分类是否合法
     *
     * @param id 产品分类编号
     */
    private void validateCategory(Long id) {
        productCategoryService.validateCategory(id);
        // 校验层级
        if (productCategoryService.getCategoryLevel(id) < CATEGORY_TOP_LEVEL_1) {
            throw exception(SPU_SAVE_FAIL_CATEGORY_LEVEL_ERROR, CATEGORY_TOP_LEVEL_1);
        }
    }

    @Override
    public List<ProductSpuDO> validateSpuList(Collection<Long> ids) {
        if (CollUtil.isEmpty(ids)) {
            return new ArrayList<>();
        }
        // 获得产品信息
        List<ProductSpuDO> list = productSpuMapper.selectByIds(ids);
        Map<Long, ProductSpuDO> spuMap = CollectionUtils.convertMap(list, ProductSpuDO::getId);
        // 校验
        ids.forEach(id -> {
            ProductSpuDO spu = spuMap.get(id);
            if (spu == null) {
                throw exception(SPU_NOT_EXISTS);
            }
            if (!ProductSpuStatusEnum.isEnable(spu.getStatus())) {
                throw exception(SPU_NOT_ENABLE, spu.getName());
            }
        });
        return list;
    }

    @Override
    public void updateBrowseCount(Long id, int incCount) {
        productSpuMapper.updateBrowseCount(id, incCount);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void deleteSpu(Long id) {
        // 校验存在
        ProductSpuDO spuDO = validateSpuExists(id);
        // 删除
        deleteSpu(spuDO);
    }

    private void deleteSpu(ProductSpuDO spuDO) {
        // 校验 SPU 状态，不是回收站不能删除
        if (ObjectUtil.notEqual(spuDO.getStatus(), ProductSpuStatusEnum.RECYCLE.getStatus())) {
            throw exception(SPU_NOT_RECYCLE);
        }
        // TODO 芋艿：【可选】参与活动中的产品，不允许删除？？？
        // 删除 SPU
        productSpuMapper.deleteById(spuDO.getId());
        // 删除关联的 SKU
        productSkuService.deleteSkuBySpuId(spuDO.getId());
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public Long createSpu(AppProductSpuSaveReqVO createReqVO) {
        ProductSpuSaveReqVO reqVO = ProductSpuConvert.INSTANCE.convert(createReqVO);
        return createSpu(reqVO);
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void updateSpu(AppProductSpuSaveReqVO updateReqVO) {
        // 校验权限
        validateSpuExists(updateReqVO.getId());
        ProductSpuSaveReqVO reqVO = ProductSpuConvert.INSTANCE.convert(updateReqVO);
        updateSpu(reqVO);
    }

    private ProductSpuDO validateSpuExists(Long id) {
        ProductSpuDO spuDO = productSpuMapper.selectById(id);
        if (spuDO == null) {
            throw exception(SPU_NOT_EXISTS);
        }
        return spuDO;
    }

    @Override
    public ProductSpuDO getSpu(Long id) {
        return productSpuMapper.selectById(id);
    }

    @Override
    public ProductSpuDO getSpu(Long id, boolean includeDeleted) {
        if (includeDeleted) {
            return productSpuMapper.selectByIdIncludeDeleted(id);
        }
        return getSpu(id);
    }

    @Override
    public AppProductSpuDetailRespVO browseSpu(Long userId, Long id) {
        // 获得产品 SPU
        ProductSpuDO spu = validateSpuExists(id);
        if (!ProductSpuStatusEnum.isEnable(spu.getStatus())) {
            throw exception(SPU_NOT_ENABLE, spu.getName());
        }
        spu.setBrowseCount(spu.getBrowseCount() + spu.getVirtualSalesCount());

        // 增加浏览量
        updateBrowseCount(id, 1);
        // 保存浏览记录
        productBrowseHistoryService.createBrowseHistory(getLoginUserId(), id);
        // 获得产品 SKU
        List<ProductSkuDO> skus = productSkuService.getSkuListBySpuId(spu.getId());

        int discountPercent = memberLevelApi.getMemberLevelDiscountPercentByUserId(getLoginUserId());
        // 拼接返回
        AppProductSpuDetailRespVO respVO = ProductSpuConvert.INSTANCE.convert3(spu);
        respVO.setDeductUnitPrice(configApiV2);
        List<AppProductSkuRespVO> skuRespVOS = new ArrayList<>(skus.size());
        for (ProductSkuDO sku : skus) {
            AppProductSkuRespVO skuRespVO = ProductSpuConvert.INSTANCE.convert(sku);
            skuRespVO.setDeductUnitPrice(configApiV2);
            skuRespVO.setMemberLevelPrice(calculateMemberLevelPrice(discountPercent, spu.getPrice()));
            skuRespVOS.add(skuRespVO);
        }
        respVO.setSkus(skuRespVOS);
        // 处理用户等级尊享价格
        respVO.setMemberLevelPrice(calculateMemberLevelPrice(discountPercent, spu.getPrice()));
        // 处理多规格价格范围
        convertPriceYuanRange(spu, skus, respVO);
        return respVO;
    }

    @Override
    public AppShopProductSpuDetailRespVO shopSpu(Long id) {
        // 获得产品 SPU
        ProductSpuDO spu = validateSpuExists(id);
        // 获得产品 SKU
        List<ProductSkuDO> skus = productSkuService.getSkuListBySpuId(spu.getId());
        if (skus == null) {
            skus = new ArrayList<>();
        }

        int discountPercent = memberLevelApi.getMemberLevelDiscountPercentByUserId(getLoginUserId());
        // 拼接返回
        AppShopProductSpuDetailRespVO respVO = ProductSpuConvert.INSTANCE.convert3(spu);
        respVO.setDeductUnitPrice(configApiV2);
        List<AppProductSkuRespVO> skuRespVOS = new ArrayList<>(skus.size());
        for (ProductSkuDO sku : skus) {
            AppProductSkuRespVO skuRespVO = ProductSpuConvert.INSTANCE.convert(sku);
            skuRespVO.setMemberLevelPrice(calculateMemberLevelPrice(discountPercent, spu.getPrice()));
            skuRespVOS.add(skuRespVO);
        }
        respVO.setSkus(skuRespVOS);
        // 处理用户等级尊享价格
        respVO.setMemberLevelPrice(calculateMemberLevelPrice(discountPercent, spu.getPrice()));
        // 处理多规格价格范围
        convertPriceYuanRange(spu, skus, respVO);
        return respVO;
    }

    @Override
    public List<ProductSpuDO> getSpuList(Collection<Long> ids) {
        if (CollUtil.isEmpty(ids)) {
            return new ArrayList<>();
        }
        List<ProductSpuDO> spus = productSpuMapper.selectByIds(ids);
        Map<Long, ProductSpuDO> spuMap = convertMap(spus, ProductSpuDO::getId);
        // 需要按照 ids 顺序返回。例如说：店铺装修选择了 [3, 1, 2] 三个产品，返回结果还是 [3, 1, 2]  这样的顺序
        return convertList(ids, spuMap::get);
    }

    @Override
    public List<ProductSpuDO> getSpuListByStatus(Integer status) {
        return productSpuMapper.selectList(new CoreLambdaQueryWrapper<ProductSpuDO>()
                .eq(ProductSpuDO::getStatus, status)
                .orderByAsc(ProductSpuDO::getSort));
    }

    @Override
    public PageResult<ProductSpuDO> getSpuPage(ProductSpuPageReqVO pageReqVO) {
        // 查找时，如果查找某个分类编号，则包含它的子分类。因为顶级分类可能不包含产品
        Set<Long> categoryIds = new HashSet<>();
        if (pageReqVO.getCategoryId() != null && pageReqVO.getCategoryId() > 0) {
            categoryIds.add(pageReqVO.getCategoryId());
            categoryIds.addAll(productCategoryService.getDescendentCategoryIdsFromCache(pageReqVO.getCategoryId(), CommonStatusEnum.ENABLE.getStatus()));
        }
        List<Long> categoryIdList = queryStringToLongs(pageReqVO.getCategoryIds());
        if (CollUtil.isNotEmpty(categoryIdList)) {
            categoryIds.addAll(categoryIdList);
            categoryIds.forEach(cid -> categoryIds.addAll(productCategoryService.getDescendentCategoryIdsFromCache(cid, CommonStatusEnum.ENABLE.getStatus())));
        }
        pageReqVO.setCategoryIds(StrUtil.join(",", categoryIds));
        return productSpuMapper.selectPage(pageReqVO);
    }

    @Override
    public PageResult<AppProductSpuRespVO> getSpuPage(AppProductSpuPageReqVO pageReqVO) {
        // 排除已封禁的店铺的产品 TODO 后续优化，增加封禁店铺产品关联表，通过定时任务同步数据关联关系
        Set<Long> disabledShopIds = shopInfoApi.getShopIdsByDisabled();
        if (ShopCategoryCodeEnum.UNDEFINED.getCode().equals(pageReqVO.getPlatformCategoryCode())) {
            // 前端代码适配
            pageReqVO.setPlatformCategoryCode("");
        }
        if (ShopCategoryCodeEnum.GROUPON.getCode().equals(pageReqVO.getPlatformCategoryCode())) {
            // 指定团购套餐
            Set<Long> shopIds = new HashSet<>();
            if (StrUtil.isNotBlank(pageReqVO.getCityCode())) {
                // 指定城市的产品
                Set<Long> cityShopIds = shopInfoApi.getShopIdsByCityCodes(CollUtil.newHashSet(pageReqVO.getCityCode()));
                if (CollUtil.isEmpty(cityShopIds)) {
                    // 指定城市没有相关店铺，则说明就没相关团购套餐
                    return PageResult.empty();
                }
                shopIds.addAll(cityShopIds);
            }
            PageResult<GrouponPackageRespDTO> page = grouponPackageApi.getPage(pageReqVO.getPageNo(), pageReqVO.getPageSize(), shopIds);
            List<GrouponPackageRespDTO> dtos = page.getList();
            List<AppProductSpuRespVO> list = new ArrayList<>(dtos.size());
            for (GrouponPackageRespDTO dto : dtos) {
                AppProductSpuRespVO respVO = ProductSpuConvert.INSTANCE.convertGrouponPackage(dto);
                respVO.setType(ShopCategoryCodeEnum.GROUPON.getCode());
                respVO.setSkus(new ArrayList<>());
                list.add(respVO);
            }
            return new PageResult<>(list, page.getTotal());
        }
        if (Boolean.TRUE.equals(pageReqVO.getSupportCryptocurrencyFullDeduction())) {
            // 支持联盟豆全额抵扣的产品
            PageResult<ProductSpuDO> pageResult = productSpuMapper.selectPageForCryptocurrencyFullDeduction(pageReqVO, disabledShopIds);
            return toPageResult(pageResult);
        }
        Set<Long> shopIds = new HashSet<>();
        if (pageReqVO.getShopId() != null && pageReqVO.getShopId() > 0) {
            // 指定店铺的产品
            shopIds.add(pageReqVO.getShopId());
        }
        List<Long> shopIdList = queryStringToLongs(pageReqVO.getShopIds());
        if (CollUtil.isNotEmpty(shopIdList)) {
            // 指定店铺的产品
            shopIds.addAll(shopIdList);
        }
        if (StrUtil.isNotBlank(pageReqVO.getCityCode())) {
            // 指定城市的产品 TODO 后续优化，增加城市产品关联表，通过定时任务同步数据关联关系
            Set<Long> cityShopIds = shopInfoApi.getShopIdsByCityCodes(CollUtil.newHashSet(pageReqVO.getCityCode()));
            if (CollUtil.isEmpty(cityShopIds)) {
                // 指定城市没有相关店铺，则说明就没相关产品
                return PageResult.empty();
            }
            shopIds.addAll(cityShopIds);
            if (CollUtil.isNotEmpty(shopIds)) {
                shopIds.removeIf(it -> !cityShopIds.contains(it));
            }
        }
        Set<Long> platformCategoryIds = new HashSet<>();
        if (StrUtil.isNotBlank(pageReqVO.getPlatformCategoryCode())) {
            // 指定主营类目的产品 TODO 后续优化，增加行业类目产品关联表，通过定时任务同步数据关联关系
            Set<Long> descendentCategoryIds = shopCategoryApi.getEnableDescendentCategoryIds(pageReqVO.getPlatformCategoryCode(), true);
            if (CollUtil.isEmpty(descendentCategoryIds)) {
                // 指定主营类目没有相关店铺，则说明就没相关产品
                return PageResult.empty();
            }
            platformCategoryIds.addAll(descendentCategoryIds);
        }
        if (pageReqVO.getPlatformCategoryId() != null && pageReqVO.getPlatformCategoryId() > 0) {
            // 指定主营类目的产品
            platformCategoryIds.add(pageReqVO.getPlatformCategoryId());
            // 查找时，如果查找某个类目编号，则包含它的子类目。因为顶级类目可能不包含产品
            Set<Long> descendentCategoryIds = shopCategoryApi.getEnableDescendentCategoryIds(pageReqVO.getPlatformCategoryId());
            if (CollUtil.isEmpty(descendentCategoryIds)) {
                platformCategoryIds.addAll(descendentCategoryIds);
            }
        }
        List<Long> platformCategoryIdList = queryStringToLongs(pageReqVO.getPlatformCategoryIds());
        if (CollUtil.isNotEmpty(platformCategoryIdList)) {
            // 指定主营类目的产品
            platformCategoryIds.addAll(platformCategoryIdList);
            // 查找时，如果查找某个主营类目编号，则包含它的子类目。因为顶级类目可能不包含产品
            platformCategoryIdList.forEach(cid -> {
                Set<Long> descendentCategoryIds = shopCategoryApi.getEnableDescendentCategoryIds(cid);
                if (CollUtil.isNotEmpty(descendentCategoryIds)) {
                    platformCategoryIds.addAll(descendentCategoryIds);
                }
            });
        }
        Set<Long> categoryIds = new HashSet<>();
        if (pageReqVO.getCategoryId() != null && pageReqVO.getCategoryId() > 0) {
            // 指定分类的产品
            categoryIds.add(pageReqVO.getCategoryId());
            // 查找时，如果查找某个分类编号，则包含它的子分类。因为顶级分类可能不包含产品
            Set<Long> descendentCategoryIds = productCategoryService.getDescendentCategoryIdsFromCache(pageReqVO.getCategoryId(), CommonStatusEnum.ENABLE.getStatus());
            if (CollUtil.isNotEmpty(descendentCategoryIds)) {
                categoryIds.addAll(descendentCategoryIds);
            }
        }
        List<Long> categoryIdList = queryStringToLongs(pageReqVO.getCategoryIds());
        if (CollUtil.isNotEmpty(categoryIdList)) {
            // 指定分类的产品
            categoryIds.addAll(categoryIdList);
            // 查找时，如果查找某个分类编号，则包含它的子分类。因为顶级分类可能不包含产品
            categoryIdList.forEach(cid -> {
                Set<Long> descendentCategoryIds = productCategoryService.getDescendentCategoryIdsFromCache(cid, CommonStatusEnum.ENABLE.getStatus());
                if (CollUtil.isNotEmpty(descendentCategoryIds)) {
                    categoryIds.addAll(descendentCategoryIds);
                }
            });
        }
        // 分页查询
        PageResult<ProductSpuDO> pageResult = productSpuMapper.selectPage(pageReqVO, shopIds, disabledShopIds, platformCategoryIds, categoryIds);
        return toPageResult(pageResult);
    }

    @Override
    public PageResult<AppProductSpuRespVO> getSpuPageByPlatformCategoryId(AppProductSpuPageForPlatformCategoryReqVO pageReqVO) {
        AppProductSpuPageReqVO newPageReqVO = BeanUtils.toBean(pageReqVO, AppProductSpuPageReqVO.class);
        newPageReqVO.setPlatformCategoryId(pageReqVO.getCategoryId());
        return getSpuPage(newPageReqVO);
    }

    @Override
    public PageResult<AppProductSpuRespVO> getSpuPageByProductCategoryId(AppProductSpuPageForProductCategoryReqVO pageReqVO) {
        // 分页查询
        AppProductSpuPageReqVO newPageReqVO = BeanUtils.toBean(pageReqVO, AppProductSpuPageReqVO.class);
        newPageReqVO.setCategoryId(pageReqVO.getCategoryId());
        return getSpuPage(newPageReqVO);
    }

    @Override
    public PageResult<AppProductSpuRespVO> getSpuPageByCloudAlliance(AppProductSpuPageForCloudAllianceReqVO pageReqVO) {
        // 分页查询
        AppProductSpuPageReqVO newPageReqVO = BeanUtils.toBean(pageReqVO, AppProductSpuPageReqVO.class);
        newPageReqVO.setSupportCryptocurrencyFullDeduction(true);
        return getSpuPage(newPageReqVO);
    }

    @Override
    public PageResult<AppProductSpuRespVO> getSpuPageByDishAttribute(AppProductSpuPageForDishAttributeReqVO pageReqVO) {
        // 分页查询
        AppProductSpuPageReqVO newPageReqVO = BeanUtils.toBean(pageReqVO, AppProductSpuPageReqVO.class);
        newPageReqVO.setDishAttribute(pageReqVO.getDishAttribute());
        return getSpuPage(newPageReqVO);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateSpuStock(Map<Long, Integer> stockIncCounts) {
        stockIncCounts.forEach((id, incCount) -> productSpuMapper.updateStock(id, incCount));
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateSpuSalesCount(Map<Long, Integer> salesCountIncCounts) {
        salesCountIncCounts.forEach((id, incCount) -> productSpuMapper.updateSalesCount(id, incCount));
    }

    @Override
    public void updateSpuStatus(ProductSpuUpdateStatusReqVO updateReqVO) {
        // 校验存在
        ProductSpuDO spuDO = validateSpuExists(updateReqVO.getId());
        // TODO 芋艿：【可选】参与活动中的产品，不允许下架？？？

        // 更新状态
        spuDO.setStatus(updateReqVO.getStatus());
        productSpuMapper.updateById(spuDO);
    }

    @Override
    public void updateSpuStatus(AppProductSpuUpdateStatusReqVO updateReqVO) {
        // 校验存在
        ProductSpuDO spuDO = validateSpuExists(updateReqVO.getId());
        // TODO 芋艿：【可选】参与活动中的产品，不允许下架？？？

        // 更新状态
        spuDO.setStatus(updateReqVO.getStatus());
        productSpuMapper.updateById(spuDO);
    }

    @Override
    public void updateSpuRecommend(ProductSpuUpdateRecommendReqVO updateReqVO) {
        // 校验存在
        ProductSpuDO spuDO = validateSpuExists(updateReqVO.getId());
        // 更新推荐类型
        spuDO.setRecommend(updateReqVO.getRecommend());
        productSpuMapper.updateById(spuDO);
    }

    @Override
    public void updateRecommendSort(ProductSpuUpdateRecommendSortReqVO updateReqVO) {
        // 校验存在
        ProductSpuDO spuDO = validateSpuExists(updateReqVO.getId());
        // 更新推荐类型
        spuDO.setRecommend(updateReqVO.getRecommend());
        // 更新排序
        spuDO.setSort(NumberUtil.nullToZero(updateReqVO.getSort()));
        productSpuMapper.updateById(spuDO);
    }

    @Override
    public void updateSpuSticky(ProductSpuUpdateStickyReqVO updateReqVO) {
        // 校验存在
        ProductSpuDO spuDO = validateSpuExists(updateReqVO.getId());
        spuDO.setIsSticky(updateReqVO.getIsSticky());
        productSpuMapper.updateById(spuDO);
    }

    @Override
    public void updateSpuRecommend(AppProductSpuUpdateRecommendReqVO updateReqVO) {
        // 校验存在
        ProductSpuDO spuDO = validateSpuExists(updateReqVO.getId());
        // 更新推荐类型
        spuDO.setRecommend(updateReqVO.getRecommend());
        productSpuMapper.updateById(spuDO);
    }

    @Override
    public void updateSpuIntegral(AppProductSpuUpdateIntegralReqVO updateReqVO) {
        // 校验存在
        ProductSpuDO spuDO = validateSpuExists(updateReqVO.getId());
        // 更新是否积分产品
        spuDO.setIsIntegral(updateReqVO.getIsIntegral());
        productSpuMapper.updateById(spuDO);
    }

    @Override
    public Map<String, Long> getSpuCount(@Nullable Long shopId, String type) {
        Map<String, Long> counts = Maps.newLinkedHashMapWithExpectedSize(5);
        // 查询销售中的产品数量
        counts.put(ProductSpuTabTypeEnum.FOR_SALE.name(),
                productSpuMapper.selectCount(shopId, type, ProductSpuStatusEnum.ENABLE.getStatus()));
        // 查询已下架的产品数量
        counts.put(ProductSpuTabTypeEnum.IN_WAREHOUSE.name(),
                productSpuMapper.selectCount(shopId, type, ProductSpuStatusEnum.DISABLE.getStatus()));
        // 查询已售罄的产品数量
        counts.put(ProductSpuTabTypeEnum.SOLD_OUT.name(),
                productSpuMapper.selectSoldOutCount(shopId, type));
        // 查询回收站中的产品数量
        counts.put(ProductSpuTabTypeEnum.RECYCLE_BIN.name(),
                productSpuMapper.selectCount(shopId, type, ProductSpuStatusEnum.RECYCLE.getStatus()));
        // 查询触发警戒库存的产品数量
        counts.put(ProductSpuTabTypeEnum.ALERT_STOCK.name(),
                productSpuMapper.selectAlertStockCount(shopId, type));
        // 查询待审核的产品数量
        counts.put(ProductSpuTabTypeEnum.WAIT_AUDIT.name(),
                productSpuMapper.selectCount(shopId, type, ProductSpuStatusEnum.WAIT_AUDIT.getStatus()));
        return counts;
    }

    @Override
    public Long getSpuCountByCategoryId(Long categoryId) {
        return productSpuMapper.selectCount(ProductSpuDO::getCategoryId, categoryId);
    }

    @Override
    public Integer calculateMemberLevelPrice(Integer discountPercent, Integer price) {
        if (discountPercent == null || discountPercent <= 0) {
            return price;
        }
        Integer newPrice = price * discountPercent / 100;
        return price - newPrice;
    }

    @DataPermission(enable = false)
    @Override
    public Long getProductCountByShopId(Long shopId) {
        return productSpuMapper.selectCount(ProductSpuDO::getShopId, shopId);
    }

    @Override
    public Map<Long, Integer> getSpuCountMapByShopIds(Collection<Long> shopIds) {
        if (CollUtil.isEmpty(shopIds)) {
            return new HashMap<>();
        }
        List<ShopIdSpuCountBO> list = productSpuMapper.selectSpuCountByShopIds(shopIds);
        return convertMap(list, ShopIdSpuCountBO::getShopId, ShopIdSpuCountBO::getSpuCount);
    }

    @Override
    public Map<Long, Integer> getShopDistanceMapByPlatformCategoryIds(Set<Long> platformCategoryIds, LocationQueryDTO location, SortQueryDTO sort) {
        if (CollUtil.isEmpty(platformCategoryIds)) {
            return new HashMap<>();
        }
        if (sort == null) {
            sort = new SortQueryDTO();
        }
        SortTypeEnum sortTypeEnum = SortTypeEnum.codeOf(sort.getSortType());
        if (location == null || location.getLatitude() == null || location.getLongitude() == null
            || (location.getLatitude() == 0D && location.getLongitude() == 0D)) {
            List<Long> list;
            if (SortTypeEnum.RANDOM.equals(sortTypeEnum)) {
                list = productSpuMapper.selectShopIdsOrderByRandom(platformCategoryIds);
            } else if (SortTypeEnum.SCORE.equals(sortTypeEnum)) {
                list = productSpuMapper.selectShopIdsOrderByScore(platformCategoryIds);
            } else if (SortTypeEnum.PRICE.equals(sortTypeEnum)) {
                list = productSpuMapper.selectShopIdsOrderByPrice(platformCategoryIds);
            } else {
                list = productSpuMapper.selectShopIdsOrderById(platformCategoryIds);
            }
            Map<Long, Integer> map = new HashMap<>();
            if (CollUtil.isEmpty(list)) {
                return map;
            }
            for (Long shopId : list) {
                map.put(shopId, -1);
            }
            log.debug("[getShopDistanceMapByPlatformCategoryIds][platformCategoryIds({}) 获得店铺编号({})]", platformCategoryIds, list);
            return map;
        }
        List<ShopIdDistanceBO> list = productSpuMapper.selectShopIdsAndDistance(platformCategoryIds, location.getLongitude(), location.getLatitude());
        log.debug("[getShopDistanceMapByPlatformCategoryIds][platformCategoryIds({}) 获得店铺编号及距离({})]", platformCategoryIds, list);
        if (CollUtil.isEmpty(list)) {
            return new HashMap<>();
        }
        return convertMap(list, ShopIdDistanceBO::getShopId, ShopIdDistanceBO::getDistanceKm);
    }

    private PageResult<AppProductSpuRespVO> toPageResult(PageResult<ProductSpuDO> pageResult) {
        List<ProductSpuDO> list = pageResult.getList();
        if (CollUtil.isEmpty(list)) {
            return PageResult.empty();
        }
        PageResult<AppProductSpuRespVO> voPageResult = ProductSpuConvert.INSTANCE.convertPage2(pageResult);
        List<AppProductSpuRespVO> voList = voPageResult.getList();
        convertDO2VO(list, voList);
        return voPageResult;
    }

    private void convertDO2VO(List<ProductSpuDO> list, List<AppProductSpuRespVO> voList) {
        // 拼接返回
        for (ProductSpuDO spu : list) {
            spu.setSalesCount(spu.getSalesCount() + spu.getVirtualSalesCount());
        }
        int discountPercent = memberLevelApi.getMemberLevelDiscountPercentByUserId(getLoginUserId());
        Set<Long> shopIds = convertSet(voList, AppProductSpuRespVO::getShopId);
        Map<Long, ShopInfoDTO> shops = shopInfoApi.getShopInfoMap(shopIds);
        Set<Long> brandIds = list.stream().map(ProductSpuDO::getBrandId).collect(Collectors.toSet());
        Map<Long, ProductBrandDO> brands = productBrandService.getBrandMap(brandIds);
        Set<Long> spuIds = list.stream().map(ProductSpuDO::getId).collect(Collectors.toSet());
        List<ProductSkuDO> skus = productSkuService.getSkuListBySpuIds(spuIds);
        log.debug("[convertDO2VO][spuIds({}) 获得 skus({})个]", spuIds, skus.size());
        Map<Long, List<ProductSkuDO>> skuMap = skus.stream().collect(Collectors.groupingBy(ProductSkuDO::getSpuId));
        for (AppProductSpuRespVO vo : voList) {
            // 处理店铺信息
            ShopInfoDTO shop = shops.get(vo.getShopId());
            vo.setShop(shop == null ? ShopInfoDTO.FALLBACK : shop);
            // 处理规格信息
            List<ProductSkuDO> skuList = skuMap.getOrDefault(vo.getId(), Collections.emptyList());
            List<AppProductSkuRespVO> skuRespVOS = new ArrayList<>(skuList.size());
            for (ProductSkuDO sku : skuList) {
                AppProductSkuRespVO skuRespVO = ProductSpuConvert.INSTANCE.convert(sku);
                skuRespVO.setDeductUnitPrice(configApiV2);
                skuRespVO.setMemberLevelPrice(calculateMemberLevelPrice(discountPercent, sku.getPrice()));
                skuRespVOS.add(skuRespVO);
            }
            vo.setSkus(skuRespVOS);
            // 处理用户等级尊享价格
            vo.setDeductUnitPrice(configApiV2);
            vo.setMemberLevelPrice(calculateMemberLevelPrice(discountPercent, vo.getPrice()));
            // 处理品牌信息
            ProductBrandDO brand = brands.get(vo.getBrandId());
            vo.setBrandName(brand == null ? "不详" : brand.getName());
        }
    }

    private void convertPriceYuanRange(ProductSpuDO spu, List<ProductSkuDO> skus, AppProductSpuDetailRespVO respVO) {
        if (CollUtil.isEmpty(skus)) {
            respVO.setPriceMinYuan(MoneyUtils.fenToYuanStr(NumberUtil.nullToZero(spu.getPrice())));
            respVO.setPriceMaxYuan(MoneyUtils.fenToYuanStr(NumberUtil.nullToZero(spu.getPrice())));
            return;
        }
        Integer minPrice = getMinValue(skus, ProductSkuDO::getPrice);
        Integer maxPrice = getMaxValue(skus, ProductSkuDO::getPrice);
        respVO.setPriceMinYuan(MoneyUtils.fenToYuanStr(NumberUtil.nullToZero(minPrice)));
        respVO.setPriceMaxYuan(MoneyUtils.fenToYuanStr(NumberUtil.nullToZero(maxPrice)));
    }

}
